package com.bitcola.security.activity.biz;

import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.bitcola.entity.ApplyLog;
import com.bitcola.entity.ColaMeBalance;
import com.bitcola.security.activity.constant.MiningType;
import com.bitcola.security.activity.entity.*;
import com.bitcola.security.activity.mapper.*;
import com.bitcola.security.auth.common.util.EncoderUtil;
import com.bitcola.security.common.constant.BalanceTypeConstants;
import com.bitcola.security.common.constant.FinancialConstant;
import com.bitcola.security.common.exception.LackBalanceException;
import com.bitcola.security.common.exception.ParamLimitException;
import com.bitcola.security.common.util.Snowflake;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

@Service

public class MiningBiz implements ApplicationRunner, Ordered {
    @Autowired
    ColaUserBalanceMapper colaUserBalanceMapper;
    @Autowired
    UserMiningMapper userMiningMapper;
    @Autowired
    BalanceChangeBiz balanceChangeBiz;
    @Autowired
    MiningPointMapper miningPointMapper;

    @Autowired
    UserInviterBiz userInviterBiz;

    @Resource
    MiningLevelMapper miningLevelMapper;

    @Autowired
    UserMiningLogMapper userMiningLogMapper;

    @Autowired
    AllowSellMapper allowSellMapper;


    public static final String totalMiningPowersKey = "totalMiningPowersKey";
    public static final String totalTransformPowersKey = "totalTransformPowersKey";

    public static final String totalMiningCount = "totalMiningCount";
    public static final String obtain = "mining_obtain";


    public static BigDecimal allowSellActor = BigDecimal.valueOf(0.10);


    Timer timer = new Timer();


    public static MiningPower miningPower = new MiningPower();
    Map<String, MiningPoint> miningPointVoMap = new ConcurrentHashMap<>(10);

    @Cached(cacheType = CacheType.LOCAL, expire = 10)
    public MiningPoint getPoint(String id) {
        return miningPointMapper.selectByPrimaryKey(id);


    }

    Map<String, UserMining> userMiningMap = new ConcurrentHashMap<>(100);
    Snowflake snowflake = new Snowflake(5, 11);
    ExecutorService executors = Executors.newFixedThreadPool(100);


    public UserMining getUserMining(String userId, String miningId) {
        var net = userInviterBiz.userNetMap.get(userId);
        var results = net.getUserMinings().stream().filter(x -> x.getId().equals(miningId)).collect(Collectors.toList());
        return results.size() > 0 ? results.get(0) : null;
    }

    public MiningPower getPower() {
        return miningPower;
    }

    public List<UserMining> getUserMinings(String userId) {

        var net = userInviterBiz.userNetMap.get(userId);
        var results = net.getUserMinings().stream().filter(UserMining::isActive).collect(Collectors.toList());
        return results;

    }


    public UserNet getUserGroupInfo(String userId) {


        var net = userInviterBiz.userNetMap.get(userId);
        return net;
    }


    public long getPointCount(String userId, String pointId) {
        var net = userInviterBiz.userNetMap.get(userId);

        return net.getUserMinings().stream().filter(x -> x.isActive() && x.getPointId().equals(pointId)).count();

    }


    @Transactional
    public UserMining applyNewMiningPoint(String userId, String pointId, boolean isApply, Date now) {

        Example example = new Example(UserMining.class);
        var net = userInviterBiz.userNetMap.get(userId);

        MiningPoint miningPointVo = miningPointVoMap.get(pointId);


        String coinCodes = miningPointVo.getPayCoinCodes();
        int index = 1;
        if (!isApply) {




            index = colaUserBalanceMapper.changeUserBanlance(userId, miningPointVo.getPayCoinCodes(), miningPointVo.getPrices().negate(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, EncoderUtil.BALANCE_KEY);
            if (index == 0) throw new LackBalanceException("");
            long count = 0;
            if (net != null) {
                count = getPointCount(userId, pointId);
            }
            if (count >= miningPointVo.getLimit()) throw new ParamLimitException("超过节点认购数量");

            if (pointId.equals("1")) {
                long h = net.getUserMinings().stream().filter(x -> x.getPointId().equals(pointId)).count();
                if (h > miningPointVo.getLimit()) throw new ParamLimitException("超过节点认购数量");
            }
            balanceChangeBiz.createChange(userId, BalanceTypeConstants.BALANCE, null, miningPointVo.getPrices().negate(), FinancialConstant.APPLY, coinCodes);

        }
        var inviter = net.getInviter();


        BigDecimal num = factor[3].multiply(miningPointVo.getPrices());
        if (inviter != null) {
            try {


                createMiningLog(inviter, coinCodes, num, now, MiningType.INVITING);
                colaUserBalanceMapper.changeUserBanlance(inviter, coinCodes, num, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, EncoderUtil.BALANCE_KEY);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
        //增加可售额度

        allowSellMapper.addAmount(miningPointVo.getPrices().multiply(allowSellActor), userId + "TSOL");
        if (userId.compareTo("46000") > 0) {
            if (net.getUserMinings().stream().filter(x -> x.getLevel() > 0).count() == 0) {
                if (inviter != null)
                    allowSellMapper.addAmount(BigDecimal.valueOf(2), inviter + "TSOL");


            }
        }


        return obtainNewPoint(userId, now, miningPointVo);

    }

    public UserMining obtainNewPoint(String userId, Date now, MiningPoint miningPointVo) {
        UserMining userMining = UserMining.builder().userId(userId).initTime(now).startTime(now).
                endTime(new Date(now.getTime() + miningPointVo.getMaxLastTime()))
                .id(snowflake.nextIdStr()).
                        level(miningPointVo.getLevel()).gained(BigDecimal.ZERO).perLastTime(miningPointVo.getLastTime())
                .maxLastTime(miningPointVo.getMaxLastTime()).power(miningPointVo.getPrices()).productionCoinCode(miningPointVo.getProductCoinCodes()).
                        transform(miningPointVo.getTransformUnit()).power(miningPointVo.getMiningPowers())
                .payCoinCode(miningPointVo.getPayCoinCodes())
                .build();

        if (userInviterBiz.userNetMap.get(userId) == null) return null;
        userMining.setPointId(miningPointVo.getId());
        userMining.setUnit(miningPointVo.getUnit());
        userMining.setName(miningPointVo.getName());
        userMiningMapper.insert(userMining);
        userInviterBiz.addPower(userMining);
        miningPower.setCurrentPower(miningPower.getCurrentPower().add(miningPointVo.getMiningPowers()));
        miningPower.setCurrentTransform(miningPower.getCurrentTransform().add(miningPointVo.getTransformUnit()));
        return userMining;
    }

    static BigDecimal[] factor = new BigDecimal[]{BigDecimal.valueOf(0.03d), BigDecimal.valueOf(0.02d), BigDecimal.valueOf(0.01d), BigDecimal.valueOf(0.04d)};

    @Transactional
    public UserMining obtain(String userId, String miningId) {
        Date now = new Date();

        var userMining = getUserMining(userId, miningId);
        synchronized (userMining) {
            if (!userMining.isActive()) throw new ParamLimitException("节点过期了");
            var net = userInviterBiz.userNetMap.get(userId);

            String coinCode = userMining.getProductionCoinCode();
            //返佣暂时没写
            long lastTime = (now.getTime() - userMining.getStartTime().getTime());

            if (!userMining.isActive()) lastTime = 0;
            if (lastTime > userMining.getPerLastTime()) lastTime = userMining.getPerLastTime();
            lastTime = lastTime / 1000;
            BigDecimal obtain = userMining.getTransform().multiply(BigDecimal.valueOf(lastTime));
            if (obtain.compareTo(BigDecimal.ZERO) <= 0) {
                userMining.setStartTime(now);
                return userMining;
            }
            balanceChangeBiz.createChange(userId, BalanceTypeConstants.MINING, null, obtain, FinancialConstant.MINING, coinCode);
            createMiningLog(userId, coinCode, obtain, now, MiningType.MINING);
            colaUserBalanceMapper.changeUserBanlance(userId, coinCode, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, obtain, EncoderUtil.BALANCE_KEY);

            net.addGained(obtain);

            userMining.setGained(userMining.getGained().add(obtain));
            userMining.setStartTime(now);
            userMiningMapper.updateByPrimaryKey(userMining);

            var inviter = net.getInviter();
            executorService.submit(() -> {
                prizeInviter(inviter, obtain, net, coinCode);

            });


            return userMining;
        }

    }

    ExecutorService executorService = Executors.newFixedThreadPool(50);

    @Transactional
    public void prizeInviter(String inviter, BigDecimal obtain, UserNet net, String coinCode) {
        int i = 0;
        Date now = new Date();
        while (inviter != null && i < 3) {
            var inviterNet = userInviterBiz.userNetMap.get(inviter);
            if (inviterNet != null) {
                if (inviterNet.getSelfPower().compareTo(net.getSelfPower()) >= 0) {
                    BigDecimal num = factor[i].multiply(obtain);
                    createMiningLog(inviter, coinCode, num, now, MiningType.INVITING);
                    colaUserBalanceMapper.changeUserBanlance(inviter, coinCode, num, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, EncoderUtil.BALANCE_KEY);


                } else {
                    createMiningLog(inviter, coinCode, factor[i].multiply(obtain), now, MiningType.FIRE);

                }
                inviter = inviterNet.getInviter();
                ++i;


            } else break;
        }


    }

    @Cached(cacheType = CacheType.LOCAL, expire = 180)
    public List<MiningLevelCondition> getAllLevelCondition() {
        return miningLevelMapper.selectAll();


    }

    public List<MiningLevelCondition> getPersonLevelCondition(String userId) {
        var conditions = getAllLevelCondition();
        Example example = new Example(UserLevel.class);
        example.createCriteria().andEqualTo("userId", userId);
        var levels = userwLevelMapper.selectByExample(example);
        Example example2 = new Example(UserLevel.class);
        example2.createCriteria().andEqualTo("inviter", userId).andEqualTo("type", "group");
        var inviterLevels = userwLevelMapper.selectByExample(example2);
        for (MiningLevelCondition miningLevelCondition : conditions) {
            for (UserLevel level : levels) {
                if (level.getType().equals(miningLevelCondition.getType()) && level.getLevel() == miningLevelCondition.getLevel()) {
                    miningLevelCondition.setObtained(true);
                }

            }

            for (UserLevel level : inviterLevels) {

                if (level.getLevel() == miningLevelCondition.getLevel() - 1 && miningLevelCondition.getType().equals("group")) {
                    miningLevelCondition.setDirectGroupNum(miningLevelCondition.getDirectGroupNum() + 1);
                }

            }
        }


        return conditions;


    }

    @Autowired
    UserwLevelMapper userwLevelMapper;

    public boolean obtainLevel(String userId, String levelId) {

        var conditions = getAllLevelCondition();
        var levelCondition = conditions.stream().filter(x -> x.getId().equals(levelId)).collect(Collectors.toList()).get(0);
        var net = userInviterBiz.userNetMap.get(userId);
        synchronized (net) {
            Example example = new Example(UserLevel.class);
            example.createCriteria().andEqualTo("type", levelCondition.getType()).andEqualTo("level", levelCondition.getLevel()).andEqualTo("userId", userId);

            long count = userwLevelMapper.selectCountByExample(example);
            if (count > 0) return false;
        }

        boolean result = false;
        switch (levelCondition.getType()) {

            case "group": {
                int mining = net.getGroupMiningNum().intValue();
                BigDecimal power = net.getGroupPower();
                if (mining >= levelCondition.getGroupMining() && power.compareTo(levelCondition.getGroupPower()) >= 0) {
                    if (levelCondition.getNeedDirectGroupNum() > 0) {

                        Example example1 = new Example(UserLevel.class);
                        example1.createCriteria().andEqualTo("inviter", userId).andEqualTo("type", levelCondition.getType()).andEqualTo("level", levelCondition.getLevel() - 1);
                        long count1 = userwLevelMapper.selectCountByExample(example1);
                        if (count1 >= levelCondition.getNeedDirectGroupNum()) {
                            prizeLevel(userId, levelCondition, net);
                            result = true;

                        }

                    } else {
                        prizeLevel(userId, levelCondition, net);
                        result = true;

                    }
                }

                break;
            }
            case "person": {
                int kyc = net.getDirectKyc().intValue();
                int mining = net.getDirectMiningNum().intValue();
                BigDecimal power = net.getChildrensPower();
                if (kyc >= levelCondition.getDirectKyc() && mining >= levelCondition.getDirectMining() && power.compareTo(levelCondition.getDirectPower()) >= 0) {
                    prizeLevel(userId, levelCondition, net);
                    result = true;

                }


                break;
            }


        }
        return result;


    }

    @Transactional
    void prizeLevel(String userId, MiningLevelCondition levelCondition, UserNet net) {
        obtainNewPoint(userId, new Date(), getPoint(levelCondition.getPointId()));
        UserLevel userLevel = new UserLevel();
        userLevel.setId(snowflake.nextIdStr());
        userLevel.setInviter(net.getInviter());
        userLevel.setType(levelCondition.getType());
        userLevel.setLevel(levelCondition.getLevel());
        userLevel.setPointId(levelCondition.getPointId());
        userLevel.setUserId(userId);
        userwLevelMapper.insert(userLevel);
    }

    @Scheduled(cron = "0 0/1 * * * ?")
    public synchronized void reloadMiningProject() {

        List<MiningPoint> miningPoints = miningPointMapper.selectAll();
        for (MiningPoint miningPoint : miningPoints) {
            miningPointVoMap.put(miningPoint.getId(), miningPoint);
        }

    }

    public void createMiningLog(String userId, String coinCode, BigDecimal amount, Date time, String type) {
        var log = MiningLog.builder().amount(amount).coinCode(coinCode).id(snowflake.nextIdStr()).time(time).userId(userId).type(type).build();
        userMiningLogMapper.insert(log);


    }

    public void updateMining() {
        long now = System.currentTimeMillis();
        long distance = (now - miningPower.getUpdateTime()) / 1000;
        if (distance > 1) {
            miningPower.setCurrentProduce(miningPower.getCurrentProduce().add(miningPower.getCurrentTransform().multiply(BigDecimal.valueOf(distance))));
        }
        miningPower.setUpdateTime(now);

    }


    @Transactional
    public void prizeNewUser(String userId) {
        var net = userInviterBiz.userNetMap.get(userId);
        MiningPoint miningPointVo = miningPointVoMap.get("0");
        if (net.getUserMinings().stream().filter(x -> x.getLevel() == 0).count() == 0) {

            obtainNewPoint(userId, new Date(), miningPointVo);
        }

        colaUserBalanceMapper.addUserBanlance(userId, "FB", BigDecimal.valueOf(50), null);
        var inviter = userInviterBiz.userNetMap.get(userId).getInviter();
        if (inviter != null) {
            var inviterNet = userInviterBiz.userNetMap.get(inviter);

            var inviterNum= inviterNet.getDirectKyc().intValue();
//            if(inviterNum<10)            colaUserBalanceMapper.addUserBanlance(inviter, "FB", BigDecimal.valueOf(2), null);
//            else if(inviterNum<30) colaUserBalanceMapper.addUserBanlance(inviter, "FB", BigDecimal.valueOf(3), null);
//            else colaUserBalanceMapper.addUserBanlance(inviter, "FB", BigDecimal.valueOf(4), null);


        }


    }

    public List<MiningPoint> getAllPoint(String userId) {
        List<MiningPoint> miningPoints = new ArrayList<>(miningPointVoMap.size() + 1);
        for (MiningPoint miningPoint : miningPointVoMap.values()) {
            if (StringUtils.isNotEmpty(userId)) {
                miningPoint.setNumbers(getPointCount(userId, miningPoint.getId()));
            }
            miningPoints.add(miningPoint);

        }
        return miningPoints;

    }

    public List<MiningLog> queryLog(String userId, int page, int size) {
        Example example = new Example(MiningLog.class);
        example.createCriteria().andEqualTo("userId", userId);
        example.orderBy("id").desc();
        var list = userMiningLogMapper.selectByExampleAndRowBounds(example, new RowBounds((page - 1) * size, size));
        return list;

    }

    @Autowired
    LabsApplyLogMapper labsApplyLogMapper;

    @Transactional
    public void ininted() {

    }

    @Transactional
    void initMining(UserMining userMining) {
        createMiningLog(userMining.getUserId(), "TSOL", userMining.getGained(), new Date(), "mining");
        colaUserBalanceMapper.setMiningBalance(userMining.getUserId() + "TSOL", userMining.getGained());
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {


        reloadMiningProject();
        long now = System.currentTimeMillis();
        if (userInviterBiz.userNetMap == null) userInviterBiz.init();
        var minings = userMiningMapper.selectAll();
        for (UserMining userMining : minings) {
            if (userMining.isActive()) {
                miningPower.setCurrentTransform(miningPower.getCurrentTransform().add(userMining.getTransform()));
                miningPower.setCurrentPower(miningPower.getCurrentPower().add(userMining.getPower()));

            }
            long lastTime = (now - userMining.getInitTime().getTime());
            if (lastTime > userMining.getMaxLastTime()) lastTime = userMining.getMaxLastTime();
            BigDecimal produce = userMining.getTransform().multiply(BigDecimal.valueOf(lastTime).divide(BigDecimal.valueOf(1000), 3, RoundingMode.DOWN));
            miningPower.setCurrentProduce(miningPower.getCurrentProduce().add(produce));
            if (userMining.getUserId() != null) {
                var net = userInviterBiz.userNetMap.get(userMining.getUserId());
                if (net == null) continue;
            }
//            net.setGained(net.getGained().add(userMining.getGained()));
        }
        miningPower.setUpdateTime(now);
        timer.schedule(new TimerTask() {
                           @Override
                           public void run() {
                               updateMining();
                           }
                       }
                , 100, 2000);
        int i = 0;

//
//
//        int [] ids={37494,10696,10696,12699,10455,10346,12508,10228
//        };
//        long sumKyc=0;
//        long sumNum=0;
//        for(int id:ids){
//            List<UserNet>level1= userInviterBiz.getLevel1(id+"");
//            List<UserNet>level2= userInviterBiz.getLevel2(id+"");
//            List<UserNet>level3= userInviterBiz.getLevel3(id+"");
//
//
//            int le1= level1.size();
//            int le2=level2.size();
//
//            long kyc1= (long) (level1.stream().filter(x->{
//                           return UserInviterBiz.isKycPass(x.getKyc());
//                        }).count()*0.8);
//            long kyc2= (long) (level2.stream().filter(x->{
//                            return UserInviterBiz.isKycPass(x.getKyc());
//                        }).count()*0.8);
//
//
//            sumKyc+=(long)(kyc1+kyc2)*3;
//            sumNum+=(long)(le1+le2)*2;
//
//            System.out.println(id+" 三代团队人数: "+(le1+le2)+" ,三代实名人数: "+(kyc1+kyc2)+",可享受半价节点额度: "+(le1+le2)*2+"u,免费节点额度: "+(kyc1+kyc2)*3+"u");
//
//        }
//        System.out.println(sumKyc);
//        System.out.println(sumNum);

//
//        String s = FileUtils.readFileToString(new File("C:\\Users\\wxpke\\Desktop\\jiedian"));
//        String ids[] = s.split("\r\n");
//
//
//        for (String ii : ids) {
//            String uid=ii.split("\t")[0];
//            BigDecimal v=new BigDecimal(ii.split("\t")[1]);
//
//
//            addBalance(uid,v);
//        }


    }

    @Transactional
    public void addBalance(String userId, BigDecimal v) {

        colaUserBalanceMapper.changeUserBanlance(userId, "TSOL", BigDecimal.ZERO, v, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, EncoderUtil.BALANCE_KEY);

//        colaUserBalanceMapper.addUserBanlance(userId, "USDT", v, null);

    }

    public void prizeKycNoMining() {
        List<String> strings = colaUserBalanceMapper.queryKycAndNoMining();

        for (String id : strings) {
            prizeNewUser(id);
        }
    }

    @Override
    public int getOrder() {
        return 50;
    }

    @Scheduled(cron = "0 0 */1 * * ?")
    public synchronized void reload() {
        userInviterBiz.init();


    }
}
